#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <cv.h>
#include <highgui.h>
#include <stdlib.h>
#include <math.h>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define IMGFOLDER "images/"
#define IMGPREFIX "img"
#define IMGEXT ".jpg"
#define IMGCOUNT 100
#define RANGE 20
#define RANGE1 20

#define JUMPPIXELS 1

using namespace std;
using namespace cv;
int search(uchar unique[][3],int count,uchar r, uchar g, uchar b)
{
  for(int i = 0;i<count;i++)
  {
    if(unique[i][0]==r && unique[i][1]==g && unique[i][2]==b)
      return i;
  }
  return -1;
}

void getMode(uchar borderR[],uchar borderG[],uchar borderB[],int len,uchar bgcolor[],int channels)
{
  uchar unique[len][3];
  int uniqueCount[len];
  int count=0;
  int maxfreq=1;
  int maxfreqind=0;
  for(int i=0;i<len;i++)
  {
    int pos;
    if((pos = search(unique,count,borderR[i],borderG[i],borderB[i]))>=0)
    {
      uniqueCount[pos]++;
      if(uniqueCount[pos]>maxfreq)
      {
	maxfreq = uniqueCount[pos];
	maxfreqind = i;
      }
    }
    else
    {
      unique[count][0]=borderR[i];
      unique[count][1]=borderG[i];
      unique[count][2]=borderB[i];
      uniqueCount[count++] = 1;
    }
  }
  bgcolor[0]=borderR[maxfreqind];
  bgcolor[1]=borderG[maxfreqind];
  bgcolor[2]=borderB[maxfreqind];
}

void getBackgroundColor(IplImage *img, uchar bgcolor[])
{
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar *data      = (uchar *)img->imageData;
  uchar borderR[2*(height+width-1)];
  uchar borderG[2*(height+width-1)];\
  uchar borderB[2*(height+width-1)];
  int count=0;
  // Top Border
  for(int i=0;i<width;i++)
  {
    borderR[count] = data[i*channels];
    borderG[count] = data[i*channels+1];
    borderB[count] = data[i*channels+2];
    count++;
  }
  // Bottom Border
  for(int i=0;i<width;i++)
  {
    borderR[count] = data[(height-1)*step+i*channels];
    borderG[count] = data[(height-1)*step+i*channels+1];
    borderB[count] = data[(height-1)*step+i*channels+2];
    count++;
  }
  // Left Border
  for(int i=0;i<height;i++)
  {
    borderR[count] = data[i*step];
    borderG[count] = data[i*step+1];
    borderB[count] = data[i*step+2];
    count++;
  }
  // Right Border
  for(int i=0;i<height;i++)
  {
    borderR[count] = data[i*step+(width-1)*channels];
    borderG[count] = data[i*step+(width-1)*channels+1];
    borderB[count] = data[i*step+(width-1)*channels+2];
    count++;
  }
  getMode(borderR,borderG,borderB,count,bgcolor,channels);
}

bool inRange(uchar val1,uchar val2, int range)
{
  int diff = abs(val1-val2);
  return (diff<range);
}

void subtractBackground(IplImage *img)
{
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar *data      = (uchar *)img->imageData;
  uchar bgcolor[channels];
  getBackgroundColor(img,bgcolor);
  for(int i=0;i<height;i++)
    for(int j=0;j<width;j++)
      if(inRange(data[i*step+j*channels],bgcolor[0],RANGE) && inRange(data[i*step+j*channels+1],bgcolor[1],RANGE) && inRange(data[i*step+j*channels+2],bgcolor[2],RANGE))
      {
	data[i*step+j*channels]=0;
	data[i*step+j*channels+1]=0;
	data[i*step+j*channels+2]=0;
      }
}

void thinChars(IplImage* img, int n)
{
  if(n==0)
    return;
  // To grayscale
  IplImage* grayImg = cvCreateImage( cvSize(img->width, img->height), IPL_DEPTH_8U, 1 );
  cvCvtColor(img, grayImg, CV_BGR2GRAY );
  
  CvMemStorage* storage = cvCreateMemStorage(0);
  CvSeq* contour = 0;

  cvThreshold(grayImg, grayImg, 1, 255, CV_THRESH_BINARY );
  cvFindContours(grayImg, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
  int count=0;
  for( ; contour != 0; contour = contour->h_next )
  {
//       CvScalar color = CV_RGB( 255, 255, 255 );
      CvScalar color = CV_RGB( 0, 0, 0);
      /* replace CV_FILLED with 1 to see the outlines */
      cvDrawContours(img, contour, color, color, -1, 1, 8 );
      count++;
  }
  cout<<"Contours:"<<count<<endl;
  cvReleaseImage(&grayImg);
  thinChars(img,n-1);
}

int cmpCvScalar(const void* arg1, const void* arg2, void* data)
{
  return ((*(CvScalar*)arg1).val[0]==(*(CvScalar*)arg2).val[0] && (*(CvScalar*)arg1).val[1]==(*(CvScalar*)arg2).val[1] && (*(CvScalar*)arg1).val[2]==(*(CvScalar*)arg2).val[2])?0:1;
}

void printSeq(CvSeq* seq)
{
  int total = seq->total;
  for(int i=0;i<total;i++)
  {
    CvScalar* ele = (CvScalar*)CV_GET_SEQ_ELEM(CvScalar,seq,i);
    cout<<"("<<ele->val[0]<<","<<ele->val[1]<<","<<ele->val[2]<<")";
  }
  cout<<endl;
}

void getModeColor(CvSeq* contour, IplImage* img, CvScalar *color)
{
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar* data   = (uchar*)img->imageData;
  int total = contour->total;
  CvMemStorage* storage = cvCreateMemStorage(0);
  CvSeq* seq = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvScalar), storage);
  for(int i=0;i<total;i++)
  {
    CvPoint *point =  (CvPoint *)CV_GET_SEQ_ELEM(CvPoint,contour,i);
    int x = point->x;
    int y = point->y;
    uchar r = data[y*step+x*channels];
    uchar g = data[y*step+x*channels+1];
    uchar b = data[y*step+x*channels+2];
    CvScalar c = cvScalar(r,g,b);
    cvSeqPush(seq,&c);
  }
  printSeq(seq);
  int maxFreq = 1;
  int maxFreqInd = 0;
  int uniqueFreq[total];
  int count = 0;
  CvMemStorage* storage1 = cvCreateMemStorage(0);
  CvSeq* unique = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvScalar), storage1);
  for(int i=0;i<total;i++)
  {
    int ind;
    if(cvSeqSearch(unique,(CvScalar*)CV_GET_SEQ_ELEM(CvScalar,seq,i),cmpCvScalar,0,&ind)==NULL)
    {
      cvSeqPush(unique,(CvScalar*)CV_GET_SEQ_ELEM(CvScalar,seq,i));
      uniqueFreq[count++]=1;
    }
    else
    {
      uniqueFreq[ind]++;
      if(uniqueFreq[ind]>maxFreq)
      {
	maxFreqInd = ind;
	maxFreq = uniqueFreq[ind];
      }
    }
  }
  CvScalar* ele = (CvScalar*)CV_GET_SEQ_ELEM(CvScalar,unique,maxFreqInd);
  (*color).val[0] = (*ele).val[0];
  (*color).val[1] = (*ele).val[1];
  (*color).val[2] = (*ele).val[2];
  printSeq(unique);
  cout<<"maxFreq:"<<maxFreq<<endl;
}

void getMeanColor(CvSeq* contour, IplImage* img, CvScalar *color)
{
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar* data   = (uchar*)img->imageData;
  int total = contour->total;
  int sumr=0,sumg=0,sumb=0;
  for(int i=0;i<total;i++)
  {
    CvPoint *point =  (CvPoint *)CV_GET_SEQ_ELEM(CvPoint,contour,i);
    int x = point->x;
    int y = point->y;
    uchar r = data[y*step+x*channels];
    uchar g = data[y*step+x*channels+1];
    uchar b = data[y*step+x*channels+2];
    sumr+=r;
    sumg+=g;
    sumb+=b;
  }

  (*color).val[0] = sumr/total;
  (*color).val[1] = sumg/total;
  (*color).val[2] = sumb/total;
}

void colorSelected(IplImage* img, IplImage* final, CvSeq* colors, CvScalar toSet)
{
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar* data   = (uchar*)img->imageData;
  int total = colors->total;
  cvZero(final);
  for(int i=0;i<height;i++)
    for(int j=0;j<width;j++)
    {
      bool flag=false;
      for(int k=0;k<colors->total;k++)
	if(inRange(data[i*step+j*channels],uchar((*(CvScalar*)CV_GET_SEQ_ELEM(CvScalar,colors,k)).val[0]),RANGE1) && inRange(data[i*step+j*channels+1],uchar((*(CvScalar*)CV_GET_SEQ_ELEM(CvScalar,colors,k)).val[1]),RANGE1) &&
	inRange(data[i*step+j*channels+2],uchar((*(CvScalar*)CV_GET_SEQ_ELEM(CvScalar,colors,k)).val[2]),RANGE1))
	{
	  final->imageData[i*step+j*channels]=toSet.val[0];
	  final->imageData[i*step+j*channels+1]=toSet.val[1];
	  final->imageData[i*step+j*channels+2]=toSet.val[2];
	  flag=true;
	  break;
	}
      if(!flag)
      {
	final->imageData[i*step+j*channels]=0;
	final->imageData[i*step+j*channels+1]=0;
	final->imageData[i*step+j*channels+2]=0;
      }
    }
}

void refineImage(IplImage* img, IplImage* orig)
{
  IplImage* grayImg = cvCreateImage( cvSize(img->width, img->height), IPL_DEPTH_8U, 1 );
  cvCvtColor(img, grayImg, CV_BGR2GRAY );
  
  CvMemStorage* storage = cvCreateMemStorage(0);
  CvSeq* contour = 0;

  cvThreshold(grayImg, grayImg, 1, 255, CV_THRESH_BINARY );
  cvFindContours(grayImg, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
  
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar* data   = (uchar*)img->imageData;
  CvMemStorage* storage1 = cvCreateMemStorage(0);
  CvSeq* colors = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvScalar), storage1);
  for( ; contour != 0; contour = contour->h_next )
  {
    CvScalar color;
//     getModeColor(contour,img,&color);
    getMeanColor(contour,img,&color);
    cout<<"Colour:("<<color.val[0]<<","<<color.val[1]<<","<<color.val[2]<<")"<<endl;
    cvSeqPush(colors,&color);
  }
  colorSelected(orig,img,colors,cvScalar(255,255,255));
  cvReleaseImage(&grayImg);
}

void drawSkeleton(IplImage* img, CvSeq* contour, CvScalar color)
{
  int height    = img->height;
  int width     = img->width;
  int step      = img->widthStep;
  int channels  = img->nChannels;
  uchar* data   = (uchar*)img->imageData;
  int total = contour->total;
  for(int i=0;i<total;i+=JUMPPIXELS)
  {
    CvPoint *point =  (CvPoint *)CV_GET_SEQ_ELEM(CvPoint,contour,i);
    int x = point->x;
    int y = point->y;
    uchar r = data[y*step+x*channels];
    uchar g = data[y*step+x*channels+1];
    uchar b = data[y*step+x*channels+2];
    CvScalar c = cvScalar(r,g,b);
    cvSeqPush(seq,&c);
  }
}

void getSkeletons(IplImage* img, int n)
{
  if(n==0)
    return;
  // To grayscale
  IplImage* grayImg = cvCreateImage( cvSize(img->width, img->height), IPL_DEPTH_8U, 1 );
  cvCvtColor(img, grayImg, CV_BGR2GRAY );
  
  CvMemStorage* storage = cvCreateMemStorage(0);
  CvSeq* contour = 0;

  cvThreshold(grayImg, grayImg, 1, 255, CV_THRESH_BINARY );
  cvFindContours(grayImg, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
  int count=0;
  for( ; contour != 0; contour = contour->h_next )
  {
//       CvScalar color = CV_RGB( 255, 255, 255 );
      CvScalar color = CV_RGB( 0, 0, 0);
      //cvDrawContours(img, contour, color, color, -1, 1, 8 );
      drawSkeleton(img,contour,color);
      count++;
  }
  cout<<"Contours:"<<count<<endl;
  cvReleaseImage(&grayImg);
  thinChars(img,n-1);
}

int main(int argc, char *argv[])
{
  IplImage *img=NULL;
  if(argc!=3)
  {
    cout<<"Usage ./CharThinning fileIndex n"<<endl;
    exit(-1);
  }
  int i=atoi(argv[1]);
  int n=atoi(argv[2]);
//   for(i=1;i<=IMGCOUNT;i++)
//   {
//     string st = "images/img";
    stringstream ss;
    ss<<"images/img";
    ss<<i;
    ss<<".jpg";
    string file;
    ss>>file;
    img=cvLoadImage(file.c_str());
  if(!img){
    printf("Could not load image file\n");
    exit(0);
  }
//   }
  
  IplImage* newimg,*img1;
  newimg=cvCreateImage(cvSize(img->width,img->height),img->depth,img->nChannels);
  img1=cvCreateImage(cvSize(img->width,img->height),img->depth,img->nChannels);
//   cvCvtColor(img,img1,CV_BGR2HSV);
  img1=img;
  cvCopy(img1,newimg);
  subtractBackground(newimg);

//   cvCopy(img,newimg,NULL);
//   cvSmooth(img,newimg,CV_MEDIAN);
//   newimg=img;
  
  //thinChars(newimg,n);
  getSkeletons(newimg,n);
  //refineImage(newimg,img1); // Get only inner colours of the chars
  
  // create a window
  cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE); 
  cvMoveWindow("mainWin", 100, 100);
  cvNamedWindow("mainWin1", CV_WINDOW_AUTOSIZE); 
  cvMoveWindow("mainWin1", 400, 100);

  // show the image
  cvShowImage("mainWin", img );
  cvShowImage("mainWin1", newimg );

  // wait for a key
  cvWaitKey(0);

  // release the image
  cvReleaseImage(&img );
  return 0;
}